import { env, window } from 'vscode';

import { IExtensionContext } from 'base/common/configuration/context';
import { log } from 'base/common/log/log';

import { HydeDocumentType } from '../code-search/search-strategy/_base/HydeDocument';
import { HydeStep } from '../code-search/search-strategy/_base/HydeStep';
import { ToolchainContextManager } from '../toolchain-context/ToolchainContextManager';
import { CreateToolchainContext, ToolchainContextItem } from '../toolchain-context/ToolchainContextProvider';
import { ActionType } from './ActionType';
import { CustomActionExecutePrompt } from './custom-action/CustomActionExecutePrompt';
import { CustomActionTemplateContext } from './custom-action/CustomActionTemplateContext';
import { VSCodeTemplateLoader } from './loader/VSCodeTemplateLoader';
import { TemplateContext } from './template/TemplateContext';
import { TemplateRender } from './template/TemplateRender';

export class PromptManager {
	private templateLoader: VSCodeTemplateLoader;

	constructor(
		extensionContext: IExtensionContext,
		private toolchainContextManager: ToolchainContextManager,
	) {
		this.templateLoader = new VSCodeTemplateLoader(extensionContext.extensionUri);
	}

	async collectToolchain(context: CreateToolchainContext): Promise<ToolchainContextItem[]> {
		return this.toolchainContextManager.collectContextItems(context);
	}

	async collectByCurrentDocument(): Promise<ToolchainContextItem[]> {
		const editor = window.activeTextEditor;
		if (!editor) {
			log('No active text editor found.');
			return [];
		}

		const document = editor.document;
		const language = document.languageId;
		const content = document.getText();
		const filename = document.fileName;

		const context: CreateToolchainContext = {
			action: 'CollectByCurrentDocument',
			language,
			content,
			filename,
		};

		return this.collectToolchain(context);
	}

	async constructContext(): Promise<CustomActionTemplateContext> {
		return Promise.reject('Not implemented');
	}

	/**
	 * Constructs a custom-action intention prompt using the [Velocity] templating engine.
	 *
	 * This function is used to generate a custom-action prompt message for an intention action based on the provided PsiElement, selected text,
	 * before and after cursor text. It uses a set of [VariableResolver]s to resolve variables within the template and populate the
	 * [VelocityContext].
	 *
	 * The [VelocityContext] is then used to evaluate the template and generate the prompt message.
	 *
	 */
	async constructCustomPrompt(): Promise<CustomActionExecutePrompt[]> {
		return [];
	}

	/**
	 * Asynchronously retrieves a Hyde template based on the provided step, Hyde document type, and template context.
	 *
	 * @param {HydeStep} step - The step of the Hyde process for which the template is needed.
	 * @param {HydeDocumentType} hydeType - The type of Hyde document for which the template is needed.
	 * @param {TemplateContext} context - The context in which the template will be used.
	 *
	 * @returns {Promise<string>} - Returns a promise that resolves to the requested template as a string.
	 *
	 * @throws {Error} - Throws an error if no template is found for the provided Hyde step and Hyde document type.
	 *
	 * @remarks
	 * - The method currently only supports English language templates.
	 * - The method uses the TemplateRender class to load and render the template.
	 *
	 * @example
	 * ```typescript
	 * const step = HydeStep.Init;
	 * const hydeType = HydeDocumentType.Markdown;
	 * const context = new TemplateContext();
	 *
	 * getHydeTemplate(step, hydeType, context)
	 *     .then(template => console.log(template))
	 *     .catch(error => console.error(error));
	 * ```
	 */
	async renderHydeTemplate(step: HydeStep, hydeType: HydeDocumentType, context: TemplateContext): Promise<string> {
		let templateRender = new TemplateRender(this.templateLoader);
		let template: string | undefined;

		// only for english in this version
		let humanLanguage = env.language;
		if (humanLanguage !== 'zh-cn') {
			humanLanguage = 'en';
		}

		template = await templateRender.getTemplate(`prompts/genius/${humanLanguage}/hyde/${hydeType}/${step}.vm`);

		if (!template) {
			throw new Error(`No template found for hyde step ${step} and hyde type ${hydeType}`);
		}

		return templateRender.render(template, context);
	}

	/**
	 * Asynchronously builds a string based on the specified ActionType and TemplateContext.
	 *
	 * @param type The ActionType enum value indicating the type of action to perform.
	 * @param context The TemplateContext object containing data to be used in the template rendering.
	 * @returns A Promise that resolves to a string generated by rendering the template with the provided context.
	 */
	async generateInstruction(type: ActionType, context: TemplateContext): Promise<string> {
		let templateRender = new TemplateRender(this.templateLoader);
		let template: string | undefined;

		// we only support for zh-cn-cn and en only, if the language is not supported, we default to en
		let humanLanguage = env.language;
		if (humanLanguage !== 'zh-cn') {
			humanLanguage = 'en';
		}

		switch (type) {
			case ActionType.AutoDoc:
				template = await templateRender.getTemplate(`prompts/genius/${humanLanguage}/code/auto-doc.vm`);
				break;
			case ActionType.AutoTest:
				template = await templateRender.getTemplate(`prompts/genius/${humanLanguage}/code/test-gen.vm`);
				break;
			case ActionType.GenApiData:
				template = await templateRender.getTemplate(`prompts/genius/${humanLanguage}/code/gen-api-data.vm`);
				break;
			case ActionType.Rename:
				template = await templateRender.getTemplate(`prompts/genius/${humanLanguage}/practises/rename.vm`);
				break;
			case ActionType.GenCommitMessage:
				template = await templateRender.getTemplate(`prompts/genius/${humanLanguage}/practises/gen-commit-msg.vm`);
				break;
			case ActionType.LlmReranker:
				template = await templateRender.getTemplate(`prompts/model/${humanLanguage}/reranker/llm-reranker.vm`);
				break;
			default:
				break;
		}

		if (!template) {
			throw new Error(`No template found for action type ${type}`);
		}

		return templateRender.render(template, context);
	}
}
